package com.softwarecraftsmen.orogeny.actions;

import com.softwarecraftsmen.orogeny.BuildScript;
import com.softwarecraftsmen.orogeny.buildEnvironments.BuildEnvironment;
import com.softwarecraftsmen.orogeny.buildLogs.BuildLog;
import static com.softwarecraftsmen.orogeny.buildLogs.Verbosity.Summary;
import com.softwarecraftsmen.orogeny.execution.SuccessOrFailure;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Failure;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Success;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectoriesAndFiles.absoluteDirectoriesAndFiles;
import com.softwarecraftsmen.orogeny.filing.*;
import static com.softwarecraftsmen.orogeny.filing.findFileFilters.AbstractFindFilesFilter.ZipOrJar;
import com.softwarecraftsmen.outputStreamWriters.OutputStreamWriter;
import org.jetbrains.annotations.NotNull;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.io.BufferedOutputStream;
import static java.lang.String.format;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import static java.util.Locale.UK;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class IndexJarAction extends AbstractAction
{
	private final AbsoluteFile indexFile;
	private final AbsolutePaths classPath;

	public IndexJarAction(final @NotNull AbsoluteFile indexFile, final @NotNull AbsolutePaths classPath)
	{
		this.indexFile = indexFile;
		this.classPath = classPath;
	}

	@NotNull
	public String describeWhatWeDo()
	{
		return "indexJar";
	}

	@NotNull
	public static SpecifyIndexFile indexJar(final @NotNull AbsoluteDirectory outputDirectory)
	{
		return new SpecifyIndexFile(outputDirectory.subDirectory("META-INF").file("INDEX.LIST"));
	}

	@NotNull
	public static SpecifyIndexFile indexJar(final @NotNull AbsoluteFile indexFile)
	{
		return new SpecifyIndexFile(indexFile);
	}

	public static final class SpecifyIndexFile
	{
		private final AbsoluteFile indexFile;

		private SpecifyIndexFile(final @NotNull AbsoluteFile indexFile) {this.indexFile = indexFile;}

		@NotNull
		public IndexJarAction withClassPath(final @NotNull AbsolutePath... classPath)
		{
			return withClassPath(absoluteDirectoriesAndFiles(classPath));
		}

		@NotNull
		public IndexJarAction withClassPath(final @NotNull AbsolutePaths... classPath)
		{
			return new IndexJarAction(indexFile, absoluteDirectoriesAndFiles(classPath));
		}
	}

	@NotNull
	public SuccessOrFailure execute(final @NotNull BuildLog buildLog, final @NotNull BuildEnvironment buildEnvironment)
	{
		buildLog.writeMessage(Summary, format(UK, "About to create index file %1$s from class path %2$s", indexFile, classPath));

		final Set<AbsoluteFile> jarFiles = classPath.findFiles(ZipOrJar);

		try
		{
			indexFile.writeData(new OutputStreamWriter()
			{
				public void write(final int outputBufferSize, final @NotNull BufferedOutputStream outputStream) throws IOException
				{
					final Writer writer = new BufferedWriter(new java.io.OutputStreamWriter(outputStream, "UTF-8"));

					writeVersion(writer);

					final Set<FileName> observedJarFiles = new LinkedHashSet<>();
					for (AbsoluteFile jarFile : jarFiles)
					{
						buildLog.writeMessage(Summary, format(UK, "Indexing file %1$s", jarFile));
						final FileName fileName = jarFile.fileName();
						if (observedJarFiles.contains(fileName))
						{
							throw new BuildScript.BuildSyntaxException(format(UK, "The jar file %1$s has already been indexed. Check you don't have the same jar files in different locations on the class path", jarFile));
						}
						writeIndexSection(writer, jarFile);
						observedJarFiles.add(fileName);
					}
				}

				private void writeVersion(final Writer writer) throws IOException
				{
					writeStartOfSection(writer, "JarIndex-Version: 1.0");
					writeEndOfSection(writer);
				}

				private void writeIndexSection(final Writer writer, final AbsoluteFile jarFile) throws IOException
				{
					final ZipFile zipFile = jarFile.openAsZipFile();
					try
					{
						writeStartOfSection(writer, jarFile.fileName());

						final Enumeration<? extends ZipEntry> entries = zipFile.entries();
						while (entries.hasMoreElements())
						{
							final ZipEntry zipEntry = entries.nextElement();
							if (zipEntry.isDirectory())
							{
								writer.write(zipEntry.getName());
								writeNewLine(writer);
							}
						}
						writeEndOfSection(writer);
					}
					finally
					{
						zipFile.close();
					}
				}

				private void writeStartOfSection(final Writer writer, final FileName jarFileName) throws IOException
				{
					jarFileName.write(writer);
					writeNewLine(writer);
				}

				private void writeStartOfSection(final Writer writer, final String headerName) throws IOException
				{
					writer.write(headerName);
					writeNewLine(writer);
				}

				private void writeEndOfSection(final Writer writer) throws IOException
				{
					writeNewLine(writer);
					writer.flush();
				}

				private void writeNewLine(final Writer writer) throws IOException {writer.write("\n");}
			});
		}
		catch (IOException e)
		{
			return Failure(e);
		}
		return Success;
	}
}
